using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Data.SqlClient;
/*
	todo
	1. If error on target the diaglog will not close.
	2. allow cancel during op
	3. hold source and target databases 

	set and display
	currently setting the connection changes it in main system
	Replace calls to misplaced functions in Conninfo with calls internal to the driver.
*/
namespace SqlCeDriver
{
	public partial class CopyTableForm : Form
	{
		// members
		private String sourceDb = "";
		private String targetDb = "";
		private String sourceTable = "";
		private String targetTable = "";

		private DbViewDriver.IConnectionConfig connectionConfig;
		private DbView.ConnectionInfo holdConnectionInfo;
		private DbView.ConnectionInfo sourceConnectionInfo;

		private bool isDataWanted = false;
		private bool useSourceSchema = true;
		// process handling - copy in progres flag
		private bool isCopying = false;
		private bool refreshData = false; // special case where table exists
		// cancel event -definition
		public delegate void CancelCopyHandler(object o, EventArgs e);
		// the event itself
		public static event CancelCopyHandler CancelIt;

		// Properties
		public String SourceDb
		{
			get { return this.sourceDb; }
			set { this.sourceDb = value; }
		}

		public String TargetDb
		{
			get { return this.targetDb; }
			set { this.targetDb = value; }
		}

		public String SourceTable
		{
			get { return this.sourceTable; }
			set { this.sourceTable = value; }
		}

		public String TargetTable
		{
			get { return this.targetTable; }
			set { this.targetTable = value; }
		}

		public bool IsDataWanted
		{
			get { return this.isDataWanted; }
		}

		// Constructor
		public CopyTableForm(DbViewDriver.IConnectionConfig connectionConfig, SqlCeDataSource source)
		{
			InitializeComponent();
			this.dataSource = source;
			this.connectionConfig = connectionConfig;
			this.holdConnectionInfo = connectionConfig.GetCurrentConnection();
			this.sourceConnectionInfo = this.holdConnectionInfo;
			// seed the database 
			this.sourceDb = this.sourceConnectionInfo.Name;
			this.targetDb = this.sourceConnectionInfo.Name;
		}

		// Event handlers
		private void CopyTableForm_Load(object sender, EventArgs e)
		{
			this.labelSourceDb.Text = this.sourceDb;
			this.labelSourceTable.Text = this.sourceTable;
			this.labelTargetDb.Text = this.targetDb;
			this.labelTargetTable.Text = this.targetTable;
			if (this.targetTable.Length == 0)
			{
				// this.checkuseCopyName.Checked = true;
				this.targetTable = this.sourceTable;
			}
			else
				this.checkuseCopyName.Checked = false;

			this.checkCopyContents.Checked = true;
			// Schema check box initialisation
			// Source table has no schema prefix
			if (this.sourceTable.IndexOf(".") == -1) 
				// so disable the control (state of this.m_UseSourceSchema will be irrelevant
				this.checkUseSourceSchema.Enabled = false;
			else
			{
				// check the name of the previous target table for schema prefix
				// and set the control accordingly
				this.useSourceSchema = (this.targetTable.IndexOf(".") > 0);
				this.checkUseSourceSchema.Checked = this.useSourceSchema;
			}

			ModifyTargetName();
			// ask for the connection directly 
			if ((this.sourceDb == this.targetDb) &&
				(this.sourceTable == this.targetTable))  // 
				this.BtnBrowse_Click(sender, e);
		}

		private void CopyTableForm_FormClosing(object sender, FormClosingEventArgs e)
		{
			// can't close while copying - press the cancel button instead
			if (this.isCopying)
				e.Cancel = true;
		}

		private void CheckuseCopyName_CheckedChanged(object sender, EventArgs e)
		{
			// 
			ModifyTargetName();
		}

		private void CheckuseSourceSchema_CheckedChanged(object sender, EventArgs e)
		{
			CheckBox chk = sender as CheckBox;
			if (chk != null) this.useSourceSchema = chk.Checked;
			ModifyTargetName();
		}

		private void BtnOk_Click(object sender, EventArgs e)
		{
			if ((this.sourceDb == this.targetDb) &&
				(this.sourceTable == this.targetTable))  // 
			{
				MessageBox.Show("Source and destination are the same", "Copy not permitted",
								 MessageBoxButtons.OK, MessageBoxIcon.Information);
			}
			else
			{
				// get contents wanted option
				this.isDataWanted = this.checkCopyContents.Checked;
				if (this.isDataWanted)
				{
					// set state to copy
					this.isCopying = true;
					// disable the controls
					CopyStatus();
				}
				// do it
				DoTableCopy2();
				if (!this.isDataWanted)
					Progress(100, 100);
			}
		}

		private void BtnCancel_Click(object sender, EventArgs e)
		{
			if (this.isCopying)
			{
				if (CancelIt != null)
				{
					this.isCopying = false;
					CancelIt(this, e);
				}
			}
			else
			{
				this.DialogResult = DialogResult.Cancel;
				this.Close();
			}
		}

		private void BtnBrowse_Click(object sender, EventArgs e)
		{
			String currentConn = this.targetDb;
			// connect
			// needs 
			this.connectionConfig.SetCurrentConnection(this.sourceConnectionInfo);
			SqlCeConnectionEditForm openDlg = new SqlCeConnectionEditForm(this.connectionConfig, this.dataSource);
			if (openDlg.ShowDialog(this) == DialogResult.OK)
			{
				this.sourceConnectionInfo = this.connectionConfig.GetCurrentConnection();
				currentConn = this.sourceConnectionInfo.Name;
			}
			this.connectionConfig.SetCurrentConnection(this.holdConnectionInfo);

			this.targetDb = currentConn;
			this.labelTargetDb.Text = this.targetDb;
		}

		// helpers
		//
		private String MakeDateTimePart()
		{
			System.DateTime now = new DateTime();
			now = DateTime.Now;
			return now.ToString("yyyyMMdd_hhmmss");
		}

		// Modify the destination name to copy a table to the same database as the source
		// eg MYTABLE to MYTABLE_2008_12_25_140503
		// plus now deal with schema
		private void ModifyTargetName()
		{
			String tableName = this.sourceTable;
			if (!this.useSourceSchema)
			{
				int pos = this.sourceTable.IndexOf(".");
				if (pos > 0)
					tableName = tableName.Substring(pos + 1);
			}
			if (this.checkuseCopyName.Checked == true)
				// decorate with a datetimestamp
				this.targetTable = String.Format("{0}_{1}", tableName, MakeDateTimePart());
			else
				this.targetTable = tableName;
			this.labelTargetTable.Text = this.targetTable;
		}

		// disable stuff while the worker thread is working
		private void CopyStatus()
		{
			// all disabled except cancel
			this.btnOk.Enabled = !this.isCopying;
			this.btnBrowse.Enabled = !this.isCopying;
			this.checkCopyContents.Enabled = !this.isCopying;
			this.checkuseCopyName.Enabled = !this.isCopying;
		}
		// possibly not needed now we are threaded
		private void CopyProgress2(object sender, System.Data.SqlClient.SqlRowsCopiedEventArgs e)
		{
			long i = e.RowsCopied;
			labelProgress.Text = String.Format("{0}", i);
			this.Refresh();
			e.Abort = false;
		}

		// declare the callback function signature for the threaded copy 
		delegate void ShowProgressDelegate(int total, int done);

		// function to use to receive the prgress data
		private void Progress(int total, int done)
		{
			this.progressBar1.Maximum = total;
			this.progressBar1.Value = done;
			labelProgress.Text = String.Format("{0}", done);
			labelCount.Text = String.Format("of {0}", total);

			if (done == total)
			{
				this.isCopying = false;
				this.DialogResult = DialogResult.OK;
				// extras for refresh
				if (this.refreshData)
					KickoffRefresh();
				this.Close();
			}
		}

		private void DoTableCopy2()
		{
			// resolve source and target connections
			String connSource = "";
			String connTarget = "";
			String tgtDb = ""; // for error report
			List<DbView.ConnectionInfo> connections = this.connectionConfig.GetConnections();
			foreach (DbView.ConnectionInfo ci in connections)
			{
				if (ci.Name == SourceDb) connSource = SqlCeDataSource.BuildConnectionString(ci);
				if (ci.Name == TargetDb)
				{
					connTarget = SqlCeDataSource.BuildConnectionString(ci);
					tgtDb = ci.Server+"."+ci.Database;
				}
			}
			if (!CheckSchemExistsOnTarget(connTarget, tgtDb, this.sourceTable))
			{
				this.isCopying = false;
				CopyStatus();
				return; // abort if required.
			}

			// New case. Check when copying to the same name that the table exists
			// Ask for confirmation and set flag
			if (CheckSetRefreshDataFlag(connTarget) == false)
			{
				this.isCopying = false;
				CopyStatus();
				return; // abort if required.
			}
			// refresh data was set in the call above
			if (this.refreshData)
			{
				TargetTable = String.Format("{0}_{1}", this.sourceTable, MakeDateTimePart());
			}

			bool copyContent = IsDataWanted;
			// only set up the worker thread if copying content. 
			if (copyContent)
			{
				// create the table 
				try 
				{
					//SqlTableSchema schema = this.m_Source.MakeSchemaFor(SourceTable);
					SqlTableSchema schema = Helper.MakeSchemaFor(SourceTable, this.dataSource.GetAccessor(), this.dataSource.ConnectionString);
					SqlServerScripter scripter = new SqlServerScripter(SourceTable, schema);
					scripter.CopyTable(this.dataSource, connTarget, SourceTable, TargetTable, false);
					// make the dialog close
					//Progress(100, 100);
				}
				catch (Exception exc)
				{
					MessageBox.Show(exc.Message, "Copy Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
					this.isCopying = false;
					CopyStatus();
					return; // abort if required.
				}
				// deal with the data
				CopyThreadClass wc = new CopyThreadClass(this, new ShowProgressDelegate(Progress), this.dataSource,
														 connSource, connTarget, SourceTable, TargetTable);

				CancelIt += wc.CancelCopy;
				// do it
				Thread t = new Thread(new ThreadStart(wc.RunProcess));
				t.IsBackground = true; //make them a daemon - prevent thread callback issues
				t.Start();
			}
			else
			{
				//SqlTableSchema schema = this.m_Source.MakeSchemaFor(SourceTable);
				SqlTableSchema schema = Helper.MakeSchemaFor(SourceTable, this.dataSource.GetAccessor(), this.dataSource.ConnectionString);
				SqlServerScripter scripter = new SqlServerScripter(SourceTable, schema);
				scripter.CopyTable(this.dataSource, connTarget, SourceTable, TargetTable,
											   copyContent/*, new System.Data.SqlClient.SqlRowsCopiedEventHandler(CopyProgress2)*/);
			}
		}

		private void KickoffRefresh()
		{
			String connSource = "";
			String connTarget = "";
			List<DbView.ConnectionInfo> connections = this.connectionConfig.GetConnections();
			foreach (DbView.ConnectionInfo ci in connections)
			{
				if (ci.Name == SourceDb) connSource = SqlCeDataSource.BuildConnectionString(ci);
				if (ci.Name == TargetDb) connTarget = SqlCeDataSource.BuildConnectionString(ci);
			}
			String sourceTable = TargetTable;
			String targetTable = SourceTable;
			
			// 1st non-threaded, but aiming to be threaded.
//			SqlTableSchema schema = Helper.MakeSchemaFor(sourceTable, connTarget);
			//SqlTableSchema schema = this.m_Source.MakeSchemaFor(targetTable);
			SqlTableSchema schema = Helper.MakeSchemaFor(targetTable, this.dataSource.GetAccessor(), this.dataSource.ConnectionString);
			SqlScriptingHelper scriptHelper = new SqlScriptingHelper(sourceTable, schema);
			
			SqlServerScripter scripter = new SqlServerScripter(targetTable, schema);

			// actually what this thinks is the source is actually the target and vv.
			String script = scripter.ScriptContentCopy(sourceTable, targetTable, scriptHelper);
			script += "\ndrop table " + sourceTable+" ;";
			script += " -- [[["+targetTable+"]]]"; // hack in target table name

			ContentRefreshThreadClass worker = new ContentRefreshThreadClass(null, this.dataSource, connTarget, script);
			worker.KickOffThread();

			MessageBox.Show(this, String.Format(SYNC_MESSAGE, sourceTable), "Synchronisation in Progress", MessageBoxButtons.OK, MessageBoxIcon.Information);
		}

		private bool CheckSchemExistsOnTarget(string connTarget, String database, String targetTable)
		{
			if (this.useSourceSchema == false) return true; // not necessary 
			// allow for schema 
			String[] tableParts = Helper.ParseTableName(targetTable);
			if (tableParts.GetLength(0) <= 1) return true; // not necessary 
			// name must be schemaa'd or else.
			String sql = String.Format("if exists(select * from sys.schemas where name = '{0}') select 'Y' else select 'N'", tableParts[1]);
			DataTable result = new Helper(this.dataSource.GetAccessor()).GenericQuery(connTarget, sql, "CHECK");
			if (result.Rows[0][0].ToString() == "N") // !Exists!
			{
				String message = String.Format(
@"Schema [ {0} ] doesn't exist on target database
{1}

Either uncheck the Keep schema box to copy the table to 
the dbo owner or abort the copy and create the schema
[ {0} ] on the target database", tableParts[1], database);
				MessageBox.Show(this, message, "Error. Can't proceed", MessageBoxButtons.OK, MessageBoxIcon.Error);
				return false;
			}
			return true;
		}

		private bool CheckSetRefreshDataFlag(string connTarget)
		{
			this.refreshData = false;
			if (this.SourceTable != this.TargetTable) return true;
			
			String sql = "SELECT TABLE_NAME from INFORMATION_SCHEMA.TABLES where TABLE_NAME = '" + TargetTable + "'";
			DataTable result = new Helper(this.dataSource.GetAccessor()).GenericQuery(connTarget, sql, "CHECK");
			if (result.Rows.Count == 1) // !Exists!
			{
				String message = String.Format(CONFIRM_SYNC, TargetTable);
				// note sets the default to No.
				DialogResult dr = MessageBox.Show(this, message, "Confirmation Required", MessageBoxButtons.YesNo,
												  MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);
				if (dr == DialogResult.No) return false; // false halts the whole operation.
				this.refreshData = true;
			}
			return true;
		}

		SqlCeDataSource dataSource = null;

		const String SYNC_MESSAGE = @"
Data has been transferred and the synchronisation process started. 
Check the destination database for table {0}.
If it is present then either the synchronisation is still running or it failed
";

		const String CONFIRM_SYNC = @"
Table {0} exists in the target database. Existing
data will be overrwritten. Do you still want to proceed?
";
	}
	
	public class ProgressEventArgs : EventArgs
	{
	}

	public class CopyThreadClass
	{
		ContainerControl container = null;
		Delegate senderDelegate = null;
		SqlCeDataSource dataSource = null;
		String sourceConnectionString;
		String targetConnectionString;
		String sourceTable;
		String targetTable;
		int rowCount = 0;
		bool isCancelled = false;

		public CopyThreadClass(ContainerControl sender, Delegate senderDelegate, SqlCeDataSource source,
							   String connSource, String connTarget,
							   String sourceTable, String targetTable)
		{
			this.container = sender;                 // ie the form with the controls to keep active
			this.senderDelegate = senderDelegate; // the function to safely invoke to update the progress
			this.dataSource = source;
			this.sourceConnectionString = connSource;
			this.targetConnectionString = connTarget;
			this.sourceTable = sourceTable;
			this.targetTable = targetTable;
		}

		public void RunProcess()
		{
			this.LocalRunProcess();  // my sample code does it this way
		}

		// invoked when cancel is pressed
		public void CancelCopy(object sender, EventArgs e)
		{
			this.isCancelled = true;
		}

		private void LocalRunProcess()
		{
			// use worker thread to copy the content
			//m_RowCount = Convert.ToInt32(TableScripter.CountTableRows(this.m_connSource, this.m_SourceTable));
			this.rowCount = (int)SqlServerScripter.CountTableRows(this.dataSource, this.sourceTable);
			// Getting source data. Have to cope with computed columns
			//SqlTableSchema schema = this.m_Source.MakeSchemaFor(this.m_SourceTable);
			SqlTableSchema schema = Helper.MakeSchemaFor(this.sourceTable, this.dataSource.GetAccessor(), this.dataSource.ConnectionString);
			String fields = this.MakeCopyFieldList(schema);
			String sql = String.Format("SELECT {1} FROM {0}", this.sourceTable, fields);
			// have to delegate it here as the true database objects use are versioned between 3.5 and 4.0
			String autoIncField = (schema.IdentityField == null) ? "" : schema.IdentityField.Name;
			// fake progress
			this.dataSource.GetAccessor().CopyTable(this.sourceConnectionString, this.sourceTable, this.targetConnectionString, this.targetTable, autoIncField, true, this.CopyProgress);
		}

		// return all fields (including auto-inc keys)
		// *except* computed columns
		String MakeCopyFieldList(SqlTableSchema schema)
		{
			String list = "";
			for (int idx = 0; idx < schema.Count; ++idx)
			{
				if (!schema[idx].IsCalc)
				{
					if (list.Length > 0) list += ", ";
					list += "["+schema[idx].Name+"]";  // escape the name to be safe
				}
			}
			return list;
		}

//		private void CopyProgress(object sender, System.Data.SqlClient.SqlRowsCopiedEventArgs e)
		private bool CopyProgress(int count, int done)
		{
			if (this.isCancelled)
			{
			    return true;
			}
			else
			{
			    this.container.BeginInvoke(this.senderDelegate, new object[] { this.rowCount, done });
			}
			return false;
		}
	}
	
	public class ContentRefreshThreadClass : WinFormThreading.WorkerBase
	{
		public ContentRefreshThreadClass(ContainerControl sender, SqlCeDataSource source, String connStr, String script) : base (sender, new WinFormThreading.ProgressStateDelegate(Callback), new WinFormThreading.ThreadProcessState())
		{
			ProcessState.UserData = new Object[] {source, connStr, script};
		}
		public static void Callback(WinFormThreading.ThreadProcessState processState)
		{
		}
		
		protected override void  DoWorkStep(WinFormThreading.ThreadProcessState processState)
		{
 			Object [] data = (Object[])processState.UserData;
			SqlCeDataSource source = data[0] as SqlCeDataSource;
			String connStr = data[1].ToString();
 			String script = data[2].ToString();
			bool success;
 			String message = new Helper(source.GetAccessor()).GenericExecute(connStr, script, out success);
			// hack reset of auto-increment
			int pos1 = script.IndexOf("[[[");
			int pos2 = script.IndexOf("]]]");
			if (pos1 > 0 && pos2 > pos1)
			{
				pos1 += 3;
				String table = script.Substring(pos1, (pos2 - pos1));
				new Helper(source.GetAccessor()).ResetAutoIdentity(connStr, table);
			}
		}
	}
}
